package com.imooc.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;

import java.util.Date;

/**
 * 接收/处理/响应客户端WebSocket请求的核心业务处理类
 *
 * @author YooLin1c
 * @version 1.0.0
 * @since 2018-03-06 13:00
 */
public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> {
    private WebSocketServerHandshaker handshaker;
    private static final String WEBSOCKET_URL = "ws://localhost:8888/websocket";

    /**
     * 服务端处理客户端WebSocket请求的核心业务方法
     *
     * @param context
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext context, Object msg) {
        //如果是客户端向服务器发送http握手请求
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(context, (FullHttpRequest) msg);
            //如果是客户端向服务器发送WebSocket连接
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(context, (WebSocketFrame) msg);
        }
    }

    /**
     * 处理客户端与服务器之间的WebSocket业务
     *
     * @param context
     * @param frame
     */
    private void handleWebSocketFrame(ChannelHandlerContext context, WebSocketFrame frame) {
        //判断是否是关闭WebSocket的指令
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(context.channel(), (CloseWebSocketFrame) frame.retain());
            //判断是否是ping指令
        } else if (frame instanceof PingWebSocketFrame) {
            context.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
            //判断是否是二进制消息，如果是抛异常
        } else if (!(frame instanceof TextWebSocketFrame)) {
            throw new RuntimeException(this.getClass().getName().concat("不支持二进制类型的消息"));
        }
        //返回应答消息
        //获取客户端向服务器发送的消息
        String requestMsg = ((TextWebSocketFrame) frame).text();
        System.out.println("服务器收到客户端消息>>>".concat(requestMsg));
        TextWebSocketFrame textFrame = new TextWebSocketFrame(
                new Date().toString().concat(context.channel().id().toString()).concat("===>>>").concat(requestMsg));
        //服务器给所有与其连接的客户端群发消息
        NettyConfig.group.writeAndFlush(textFrame);
    }

    /**
     * 处理客户端向服务器发起的HTTP握手请求的业务
     *
     * @param context
     * @param request
     */
    private void handleHttpRequest(ChannelHandlerContext context, FullHttpRequest request) {
        //判断不是客户端向服务器发起的HTTP握手请求
        if (!request.getDecoderResult().isSuccess() || !("websocket".equals(request.headers().get("Upgrade")))) {
            sendHttpResponse(context, request,
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(
                WEBSOCKET_URL, null, false);
        handshaker = factory.newHandshaker(request);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(context.channel());
            return;
        }
        handshaker.handshake(context.channel(), request);
    }

    /**
     * 服务端向客户端响应消息
     *
     * @param context
     * @param request
     * @param response
     */
    private void sendHttpResponse(ChannelHandlerContext context, FullHttpRequest request, FullHttpResponse response) {
        //请求失败
        if (response.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            //释放资源
            buf.release();
        }
        //服务端向客户端发送数据
        ChannelFuture future = context.channel().writeAndFlush(response);
        if (response.getStatus().code() != 200) {
            //关闭连接
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 客户端与服务端创建连接时调用
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        //将ChannelHandler保存至ChannelGroup中
        NettyConfig.group.add(ctx.channel());
        System.out.println("客户端与服务端连接开启...");
    }

    /**
     * 客户端与服务端断开连接时调用
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        //将ChannelHandler从ChannelGroup中移除
        NettyConfig.group.remove(ctx.channel());
        System.out.println("客户端与服务端断开连接...");
    }

    /**
     * 服务端接收客户端发送过来的数据结束之后调用
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    /**
     * 应用出现异常时调用
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        //打印错误信息
        cause.printStackTrace();
        //关闭连接
        ctx.close();
    }
}
